(function () {
  'use strict';
  if (window.__TED_SUN_WIDGET_V240__) return;
  window.__TED_SUN_WIDGET_V240__ = true;

  const CFG = {
    rootSel: '.ted-sun-widget',
    trainingRe: /\/teach\/control\/stream\/view\/id\/\d+/,
    lessonRe: /\/teach\/control\/lesson\/view\/id\/\d+/,

    // не трогаем админку/меню/сайдбар — только читаем DOM
    bannedClosest: [
      '.gc-account-leftbar','header','footer',
      '.breadcrumb','.breadcrumbs','.gc-breadcrumbs',
      '.dropdown','.dropdown-menu','.btn-group','[role="menu"]',
      '.modal','.popover'
    ].join(','),

    contentRoot: ['#gcAccountContent','#content','.gc-main-content','.page-content','.container','body'].join(','),

    doneClasses: ['user-state-accomplished','user-state-answered','user-state-finished','user-state-completed'],
    reachedClass: 'user-state-reached',
    missionClass: 'user-state-has_mission',
    checkIconSel: '.fa-check, .glyphicon-ok, .icon-check',

    maxDepth: 6,
    ttl: 20 * 60 * 1000,
    cacheAggKey: 'ted_sun_v240_agg',
    cacheSnapKey: 'ted_sun_v240_snap',
    concurrency: 4
  };

  const clamp = (n,a,b)=>Math.max(a, Math.min(b, n));

  function root(doc){ return (doc||document).querySelector(CFG.contentRoot) || (doc||document).body; }
  function inBanned(el){ return el && el.closest && el.closest(CFG.bannedClosest); }

  function load(key){ try { return JSON.parse(localStorage.getItem(key) || '{}'); } catch(e){ return {}; } }
  function save(key, obj){ try { localStorage.setItem(key, JSON.stringify(obj)); } catch(e){} }
  function getCached(obj, id){
    const v = obj[id];
    if (!v) return null;
    if ((Date.now()-v.ts) > CFG.ttl) return null;
    return v.payload || null;
  }
  function setCached(obj, key, id, payload){
    obj[id] = { payload, ts: Date.now() };
    save(key, obj);
  }

  function resolveHref(href, base){ try { return new URL(href, base).toString(); } catch(e){ return null; } }

  function canonTraining(url, base){
    try{
      const u = new URL(url, base||location.href); u.hash=''; u.search='';
      const m = u.pathname.match(CFG.trainingRe);
      if (m) u.pathname = m[0];
      return u.toString();
    }catch(e){
      const s = String(url||'').split('#')[0].split('?')[0];
      const m2 = s.match(CFG.trainingRe);
      return m2 ? m2[0] : s;
    }
  }

  function canonLesson(url, base){
    try{
      const u = new URL(url, base||location.href); u.hash=''; u.search='';
      const m = u.pathname.match(CFG.lessonRe);
      if (m) u.pathname = m[0];
      return u.toString();
    }catch(e){
      const s = String(url||'').split('#')[0].split('?')[0];
      const m2 = s.match(CFG.lessonRe);
      return m2 ? m2[0] : s;
    }
  }

  function idFrom(url){
    const m = String(url).match(/\/id\/(\d+)/);
    return m ? m[1] : String(url);
  }

  function pluralRu(n, one, few, many){
    n = Math.abs(n) % 100;
    const n1 = n % 10;
    if (n > 10 && n < 20) return many;
    if (n1 > 1 && n1 < 5) return few;
    if (n1 === 1) return one;
    return many;
  }
  const wTrain = n => pluralRu(n,'тренинг','тренинга','тренингов');
  const wLevel = n => pluralRu(n,'уровень','уровня','уровней');
  const wLesson = n => pluralRu(n,'урок','урока','уроков');

  async function fetchDoc(url){
    const r = await fetch(url, { credentials:'include' });
    if (!r.ok) throw new Error('HTTP '+r.status);
    const html = await r.text();
    return new DOMParser().parseFromString(html,'text/html');
  }

  function looksLockedFromDom(a){
    const txt = (a.textContent||'').toLowerCase();
    if (txt.includes('доступ закрыт')) return true;
    if (a.getAttribute('aria-disabled') === 'true') return true;
    const cls = (a.className||'').toLowerCase();
    if (cls.includes('disabled')) return true;

    const box = a.closest('li, tr, .row, .stream, .training, .page-content, .block, .panel') || a.parentElement;
    const btxt = (box && box.textContent ? box.textContent.toLowerCase() : '');
    if (btxt.includes('доступ закрыт') || btxt.includes('нет доступа')) return true;

    return false;
  }

  function looksDeniedFromDoc(doc){
    const t = (root(doc).textContent || '').toLowerCase();
    return t.includes('доступ закрыт') || t.includes('нет доступа') || t.includes('доступ к тренингу закрыт');
  }

  function stateCarrier(a){
    const c = a.closest ? (a.closest('li, tr, .lesson, .stream-table, .training') || a) : a;
    const cn = String(c.className||'');
    if (cn.includes('user-state-')) return c;
    const inner = c.querySelector ? c.querySelector('[class*="user-state-"]') : null;
    return inner || c;
  }

  function hasAnyClass(el, arr){
    if (!el || !el.classList) return false;
    for (let i=0;i<arr.length;i++) if (el.classList.contains(arr[i])) return true;
    return false;
  }

  function isDone(carrier){
    if (!carrier) return false;
    if (hasAnyClass(carrier, CFG.doneClasses)) return true;
    if (carrier.classList && carrier.classList.contains(CFG.reachedClass)){
      if (!carrier.classList.contains(CFG.missionClass)) return true;
    }
    if (carrier.classList && carrier.classList.contains(CFG.missionClass)) return false;
    if (carrier.querySelector && carrier.querySelector(CFG.checkIconSel)) return true;
    return false;
  }

  function lessonLinks(doc, base){
    const anchors = Array.from(root(doc).querySelectorAll('a[href]'));
    const out = [];
    for (const a of anchors){
      if (inBanned(a)) continue;
      const abs = resolveHref(a.getAttribute('href'), base);
      if (!abs) continue;
      const c = canonLesson(abs, base);
      if (!CFG.lessonRe.test(c)) continue;
      out.push({ a, url: c });
    }
    const seen = new Set();
    return out.filter(x => (seen.has(x.url) ? false : (seen.add(x.url), true)));
  }

  function countLessons(doc, base){
    const links = lessonLinks(doc, base);
    let total = links.length;
    let done = 0;
    for (const x of links){
      if (isDone(stateCarrier(x.a))) done++;
    }
    done = clamp(done, 0, total);
    return { done, total };
  }

  function childTrainings(doc, base){
    const anchors = Array.from(root(doc).querySelectorAll('a[href]'));
    const baseCanon = canonTraining(base, base);
    const baseId = idFrom(baseCanon);
    const byId = new Map();

    for (const a of anchors){
      if (inBanned(a)) continue;
      const abs = resolveHref(a.getAttribute('href'), base);
      if (!abs) continue;

      const c = canonTraining(abs, base);
      if (!CFG.trainingRe.test(c)) continue;

      const id = idFrom(c);
      if (id === baseId) continue;

      if (!byId.has(id)) byId.set(id, c);
    }
    return Array.from(byId.values());
  }

  async function getTrainingsFromIndex(indexUrl){
    const doc = await fetchDoc(indexUrl);
    const anchors = Array.from(root(doc).querySelectorAll('a[href]'));
    const byId = new Map();

    for (const a of anchors){
      if (inBanned(a)) continue;
      const abs = resolveHref(a.getAttribute('href'), indexUrl);
      if (!abs) continue;

      const c = canonTraining(abs, indexUrl);
      if (!CFG.trainingRe.test(c)) continue;

      const id = idFrom(c);
      if (!byId.has(id)) byId.set(id, { url: c, domLocked: looksLockedFromDom(a) });
    }
    return Array.from(byId.values());
  }

  async function snapshot(url, snapCache){
    const c = canonTraining(url, url);
    const id = idFrom(c);

    const cached = getCached(snapCache, id);
    if (cached) return cached;

    const doc = await fetchDoc(c);
    const lessons = countLessons(doc, c);
    const children = childTrainings(doc, c);
    const denied = looksDeniedFromDoc(doc);

    const payload = { url: c, lessons, children, denied };
    setCached(snapCache, CFG.cacheSnapKey, id, payload);
    return payload;
  }

  const inFlight = new Map();
  async function aggregate(url, depth, visited, aggCache, snapCache){
    const c = canonTraining(url, url);
    const id = idFrom(c);
    if (visited.has(id)) return { done:0, total:0 };
    visited.add(id);

    const cached = getCached(aggCache, id);
    if (cached) return cached;

    if (inFlight.has(id)) return await inFlight.get(id);

    const p = (async ()=>{
      try{
        const snap = await snapshot(c, snapCache);

        if (snap.lessons.total > 0){
          const leaf = { done: snap.lessons.done, total: snap.lessons.total };
          setCached(aggCache, CFG.cacheAggKey, id, leaf);
          return leaf;
        }

        if (depth >= CFG.maxDepth || !snap.children.length){
          const empty = { done:0, total:0 };
          setCached(aggCache, CFG.cacheAggKey, id, empty);
          return empty;
        }

        const sum = { done:0, total:0 };
        for (const child of snap.children){
          const r = await aggregate(child, depth+1, visited, aggCache, snapCache);
          sum.done += r.done;
          sum.total += r.total;
        }

        setCached(aggCache, CFG.cacheAggKey, id, sum);
        return sum;
      } finally {
        inFlight.delete(id);
      }
    })();

    inFlight.set(id, p);
    return await p;
  }

  function icon(kind){
    if (kind === 'train') return `
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
        <path d="M7 4h10a2 2 0 0 1 2 2v14H5V6a2 2 0 0 1 2-2Z" stroke="white" stroke-width="2" opacity=".95"/>
        <path d="M8 8h8M8 12h8M8 16h6" stroke="white" stroke-width="2" stroke-linecap="round" opacity=".95"/>
      </svg>`;
    if (kind === 'level') return `
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
        <path d="M7 10h10v4a5 5 0 0 1-10 0v-4Z" stroke="white" stroke-width="2" opacity=".95"/>
        <path d="M9 6h6" stroke="white" stroke-width="2" stroke-linecap="round" opacity=".95"/>
      </svg>`;
    return `
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
        <path d="M10 8l8 4-8 4V8Z" fill="white" opacity=".95"/>
        <path d="M5 7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V7Z" stroke="white" opacity=".65"/>
      </svg>`;
  }

  function animateNumber(el, to){
    to = Number(to||0);
    let from = Number(el.getAttribute('data-prev') || el.textContent || 0);
    if (!isFinite(from)) from = 0;

    const start = performance.now();
    const dur = 520;

    function tick(t){
      const p = Math.min(1, (t-start)/dur);
      const ease = 1 - Math.pow(1-p, 3);
      el.textContent = String(Math.round(from + (to-from)*ease));
      if (p < 1) requestAnimationFrame(tick);
      else el.setAttribute('data-prev', String(to));
    }
    requestAnimationFrame(tick);
  }

  function setRing(host, done, total){
    const ring = host.querySelector('[data-kpi="ring"]');
    if (!ring) return;
    const r = Number(ring.getAttribute('r')) || 78;
    const circ = 2 * Math.PI * r;
    const p = total > 0 ? clamp(done/total, 0, 1) : 0;
    ring.style.strokeDasharray = String(circ);
    ring.style.strokeDashoffset = String(circ * (1 - p));
  }

  function render(host){
    host.innerHTML = `
      <div class="tsw-card">
        <div class="tsw-grid">
          <div class="tsw-orb">
            <svg class="tsw-ring" viewBox="0 0 200 200" aria-hidden="true">
              <circle cx="100" cy="100" r="78" stroke="rgba(15,23,42,.08)" stroke-width="12" fill="none"></circle>
              <circle data-kpi="ring" cx="100" cy="100" r="78" stroke="rgba(37,99,235,.95)" stroke-width="12" stroke-linecap="round" fill="none"></circle>
            </svg>

            <div class="tsw-center">
              <div class="tsw-num" data-kpi="done">0</div>
              <div class="tsw-small">уроков</div>
              <div class="tsw-sub" data-kpi="sub">из 0 уроков</div>
            </div>
          </div>

          <div>
            <div class="tsw-rays">
              <div class="tsw-ray tsw-train">
                <div class="tsw-left">
                  <div class="tsw-badge">${icon('train')}</div>
                  <div class="tsw-title">тренинги</div>
                </div>
                <div class="tsw-right">
                  <span class="tsw-val" data-kpi="tAvail">0</span>
                  <span class="tsw-unit" data-kpi="tUnit">тренингов</span>
                </div>
              </div>

              <div class="tsw-ray tsw-level">
                <div class="tsw-left">
                  <div class="tsw-badge">${icon('level')}</div>
                  <div class="tsw-title">уровни</div>
                </div>
                <div class="tsw-right">
                  <span class="tsw-val" data-kpi="lAvail">0</span>
                  <span class="tsw-unit" data-kpi="lUnit">уровней</span>
                </div>
              </div>

              <div class="tsw-ray tsw-lesson">
                <div class="tsw-left">
                  <div class="tsw-badge">${icon('lesson')}</div>
                  <div class="tsw-title">пройдено уроков</div>
                </div>
                <div class="tsw-right">
                  <span class="tsw-val" data-kpi="uDone">0</span>
                  <span class="tsw-unit" data-kpi="uDoneUnit">уроков</span>
                </div>
              </div>
            </div>

            <div class="tsw-summary">
              <h4>Сводка обучения</h4>
              <p class="tsw-line" data-kpi="sumAvail">Доступно: 0 тренингов, 0 уровней, 0 уроков</p>
              <p class="tsw-line" data-kpi="sumLocked">Недоступно: 0 тренингов, 0 уровней, 0 уроков</p>
              <p class="tsw-line" data-kpi="sumDone">Пройдено: 0 уроков (из 0)</p>
            </div>
          </div>
        </div>
      </div>
    `;
  }

  function update(host, s){
    const done = s.lessonsDoneAvail || 0;
    const total = s.lessonsTotalAvail || 0;

    const tAvail = s.trainingsAvail || 0;
    const tLock  = s.trainingsLocked || 0;

    const lAvail = s.levelsAvail || 0;
    const lLock  = s.levelsLocked || 0;

    const uLock  = s.lessonsTotalLocked || 0;

    // круг: пройдено уроков из доступных уроков
    const elDone = host.querySelector('[data-kpi="done"]');
    if (elDone) animateNumber(elDone, done);

    const sub = host.querySelector('[data-kpi="sub"]');
    if (sub) sub.textContent = `из ${total} ${wLesson(total)}`;

    setRing(host, done, total);

    // лучи
    const elTA = host.querySelector('[data-kpi="tAvail"]');
    const elLA = host.querySelector('[data-kpi="lAvail"]');
    const elUD = host.querySelector('[data-kpi="uDone"]');

    if (elTA) animateNumber(elTA, tAvail);
    if (elLA) animateNumber(elLA, lAvail);
    if (elUD) animateNumber(elUD, done);

    const tUnit = host.querySelector('[data-kpi="tUnit"]');
    const lUnit = host.querySelector('[data-kpi="lUnit"]');
    const uDoneUnit = host.querySelector('[data-kpi="uDoneUnit"]');

    if (tUnit) tUnit.textContent = wTrain(tAvail);
    if (lUnit) lUnit.textContent = wLevel(lAvail);
    if (uDoneUnit) uDoneUnit.textContent = wLesson(done);

    // сводка
    const sumAvail = host.querySelector('[data-kpi="sumAvail"]');
    const sumLocked = host.querySelector('[data-kpi="sumLocked"]');
    const sumDone = host.querySelector('[data-kpi="sumDone"]');

    if (sumAvail) sumAvail.textContent =
      `Доступно: ${tAvail} ${wTrain(tAvail)}, ${lAvail} ${wLevel(lAvail)}, ${total} ${wLesson(total)}`;

    if (sumLocked) sumLocked.textContent =
      `Недоступно: ${tLock} ${wTrain(tLock)}, ${lLock} ${wLevel(lLock)}, ${uLock} ${wLesson(uLock)}`;

    if (sumDone) sumDone.textContent =
      `Пройдено: ${done} ${wLesson(done)} (из ${total})`;
  }

  function indexUrlFor(host){
    const explicit = host.getAttribute('data-index-url');
    if (explicit) { try { return new URL(explicit, location.origin).toString(); } catch(e){} }

    const p = location.pathname;
    const m = p.match(/^(.*\/teach\/control\/stream)(?:\/view\/id\/\d+)?/);
    if (m && m[1]) return location.origin + m[1];
    return location.origin + '/teach/control/stream';
  }

  async function pool(items, limit, worker){
    const ret = [];
    let i = 0;
    const runners = new Array(Math.min(limit, items.length)).fill(0).map(async ()=>{
      while (i < items.length){
        const idx = i++;
        ret[idx] = await worker(items[idx], idx);
      }
    });
    await Promise.all(runners);
    return ret;
  }

  async function compute(host){
    const idx = indexUrlFor(host);
    const aggCache = load(CFG.cacheAggKey);
    const snapCache = load(CFG.cacheSnapKey);

    let trainings = [];
    try{
      trainings = await getTrainingsFromIndex(idx);
    }catch(e){
      if (CFG.trainingRe.test(location.pathname)) trainings = [{ url: canonTraining(location.href, location.href), domLocked:false }];
      else throw e;
    }

    const s = {
      trainingsTotal: trainings.length,
      trainingsAvail: 0,
      trainingsLocked: 0,

      levelsAvail: 0,
      levelsLocked: 0,

      lessonsTotalAvail: 0,
      lessonsTotalLocked: 0,
      lessonsDoneAvail: 0
    };

    update(host, s);

    await pool(trainings, CFG.concurrency, async (tObj)=>{
      const url = tObj.url;
      let snap;
      try{
        snap = await (async ()=> {
          const c = canonTraining(url, url);
          const id = idFrom(c);
          const cached = getCached(snapCache, id);
          if (cached) return cached;

          const doc = await fetchDoc(c);
          const lessons = countLessons(doc, c);
          const children = childTrainings(doc, c);
          const denied = looksDeniedFromDoc(doc);

          const payload = { url: c, lessons, children, denied };
          setCached(snapCache, CFG.cacheSnapKey, id, payload);
          return payload;
        })();
      }catch(e){
        s.trainingsLocked += 1;
        update(host, s);
        return;
      }

      const denied = !!snap.denied;
      const locked = !!tObj.domLocked || denied;

      const levelsCount = (snap.children || []).length;

      const agg = await aggregate(url, 0, new Set(), aggCache, snapCache);
      const lessonsTotal = agg.total;
      const lessonsDone  = agg.done;

      if (locked){
        s.trainingsLocked += 1;
        s.levelsLocked += levelsCount;
        s.lessonsTotalLocked += lessonsTotal;
      } else {
        s.trainingsAvail += 1;
        s.levelsAvail += levelsCount;
        s.lessonsTotalAvail += lessonsTotal;
        s.lessonsDoneAvail += lessonsDone;
      }

      update(host, s);
    });

    s.trainingsTotal = trainings.length;
    if (s.trainingsAvail + s.trainingsLocked !== s.trainingsTotal){
      s.trainingsLocked = Math.max(0, s.trainingsTotal - s.trainingsAvail);
    }

    update(host, s);
  }

  function init(){
    const nodes = Array.from(document.querySelectorAll(CFG.rootSel));
    for (const host of nodes){
      render(host);
      update(host, {
        trainingsTotal:0, trainingsAvail:0, trainingsLocked:0,
        levelsAvail:0, levelsLocked:0,
        lessonsTotalAvail:0, lessonsTotalLocked:0, lessonsDoneAvail:0
      });
      compute(host).catch(()=>{});
    }
  }

  window.addEventListener('load', () => setTimeout(init, 200));

  window.TEDSunWidget = {
    clearCache(){
      try { localStorage.removeItem(CFG.cacheAggKey); } catch(e){}
      try { localStorage.removeItem(CFG.cacheSnapKey); } catch(e){}
      console.log('[TEDSunWidget] cache cleared');
    }
  };
})();
